/**
 * @file crop_ss_seq_seq.cpp
 * @author Andreas Bok Andersen
 * @brief CRoP SpaceSaving with sequential read and sequential reduction
 */

#include "crop.cpp"
#include <stdio.h>
#include <string.h>
#include <mpi.h>
#define MPI_H
#include "fileparser.cpp"

template<class T, class U>
void run(int argc, char *argv[]) {
	int rank, comm_size, k = 2;
	double start, end, split, crop_t = 0.0000;
	std::string FILENAME, OFILENAME;

	MPI_Status status;
	MPI_Init(&argc, &argv);
	MPI_Comm_rank(MPI_COMM_WORLD, &rank);
	MPI_Comm_size(MPI_COMM_WORLD, &comm_size);

	CmdParser cp;
	if (!cp.parseargs(argc, argv)) {
		MPI_Abort(MPI_COMM_WORLD, -1);
	}
	if (rank == 0) {
		cp.printargs();
		cout << cp.get_cmdargs() << endl;
	}

	FILENAME = DATADIR + cp.a.get < string > (INFILE);
	const char * FILEPATH = FILENAME.c_str();
	k = cp.a.get<int>(KSIZE);

	fprintf(stderr, "rank=%d, commsize=%d\n", rank, comm_size);

	MPI_Datatype entrytype;
	ENTRY_TYPE(&entrytype);

	MPI_Datatype byte_type;
	Crop<T> *crop = new Crop<T>(rank, comm_size, k);
	U type;
	start = MPI_Wtime();
	if (rank == 0) {
		MMapFILEParser<U> fp((char*) FILEPATH); // Change to command arg

		int counts = fp.NNZ_SIZE + 1;
		MPI_Bcast(&counts, 1, MPI_INT, 0, MPI_COMM_WORLD);
		MPI_Bcast(fp.COUNTS, fp.NNZ_SIZE + 1, MPI_INT, 0, MPI_COMM_WORLD);
		printf("Broadcast header\n", "");
		int i = 0;

		while (fp.getinput()) {
			/*fprintf(stderr,"fp.count %d\n", fp.count);
			fprintf(stderr,"sfp.Stride %d\n", fp.STRIDE);*/
			create_MPI_Datatype(&byte_type, fp.count, fp.STRIDE , type);

			MPI_Bcast((void*) fp.CURR_PTR, 1, byte_type, 0, MPI_COMM_WORLD);
			//printf("Broadcast row %d: %d\n",i, fp.count);
			split = MPI_Wtime();
			crop->crop((int*) fp.CURR_PTR, fp.NUM_PTR, fp.count);
			crop_t += MPI_Wtime() - split;
			//printf("Cropped row %d\n",i);
			i++;
		}

	} else {

		int NNZ_SIZE; // NNZ_SIZE is the number of the nnz row/cols and a trailing max count
		MPI_Bcast(&NNZ_SIZE, 1, MPI_INT, 0, MPI_COMM_WORLD);

		int * COUNTS = (int*) malloc(NNZ_SIZE * sizeof(int));
		MPI_Bcast(COUNTS, NNZ_SIZE, MPI_INT, 0, MPI_COMM_WORLD);

		/*fprintf(stderr,"JCSIZE@%d: %d \n", rank,NNZ_SIZE);
		 * fprintf(stderr,"COUNTS_MAX@%d: %d \n", rank,(int)COUNTS[NNZ_SIZE-1]);
		 fprintf(stderr,"COUNTS@%d: %d \n", 333,(int)COUNTS[333]);
		 fprintf(stderr,"COUNTS@%d: %d \n", 996,(int)COUNTS[996]);*/

		/*for (int i = 0; i < NNZ_SIZE - 1; i++) {
			if (COUNTS[i] > COUNTS[NNZ_SIZE - 1] || COUNTS[i] < 1) {
				fprintf(stderr, "COUNTS failed at %d: %ld", i, COUNTS[i]);
				MPI_Finalize();
				exit(-1);
			}
		}*/
		//fprintf(stderr, "passed counts check\n", "");
		int i = 0;

		int * iptr;
		float * fptr;
		void * buf = malloc(2 * sizeof(int) * COUNTS[NNZ_SIZE - 1]);
		while (i < (NNZ_SIZE - 1)) { // max was added to NNZ_SIZE
			create_MPI_Datatype(sizeof(int) * COUNTS[i], &byte_type, type);
			//buf =  malloc(2 * sizeof(int) * COUNTS[i]);
			//fprintf(stderr, "created resized dt type \n", "");

			//buf = realloc(buf, 2 * sizeof(int) * COUNTS[i]);
			/*MPI_Bcast(buf, (2* sizeof(int) * COUNTS[i]), byte_type, 0,
			 MPI_COMM_WORLD);*/
			MPI_Bcast(buf, 1, byte_type, 0, MPI_COMM_WORLD);

			//printf("Received row %d : %d\n", i, COUNTS[i]);
			iptr = (int*) buf;
			fptr = (float*) (iptr + COUNTS[i]);
			split = MPI_Wtime();
			crop->crop(iptr, fptr, COUNTS[i]);
			crop_t += MPI_Wtime() - split;
			//printf("CROPPED row %d : %d\n", i, COUNTS[i]);
			i++;

		}
		free(buf);
		free(COUNTS);

		//printf("FINISED BROADCAST RECEIVE rank: %d\n", rank);
	}
	MPI_Type_free(&byte_type);
	end = MPI_Wtime();
	fprintf(stdout, "%d\t%d\tCROP\t%.8f\n", comm_size, rank, crop_t);
	fprintf(stdout, "%d\t%d\tBCAST\t%.8f\n", comm_size, rank, end - start);

	// Pad with empty Entries if summarysize < k
	int summarysize = crop->summarysize();
	Entry *entries = (Entry*) malloc(k * sizeof(Entry));
	crop->get_summary(entries);
	fprintf(stderr, "rank: %d %s #%d\n", rank, "crop_ss.got_summary",
			summarysize);
	for (int i = summarysize; i < k; i++) {
		entries[i] = *new Entry();
		//cerr << "adding entry: " << entries[i].i << endl;
	}

	/*for (int i = 0; i < sizeof(entries) / sizeof(Entry); i++) {
	 cerr << "rank=" << rank << " : " << entries[i].to_string() << endl;
	 }*/

	Entry *resv_buf = NULL;
	if (rank == 0) {
		resv_buf = (Entry*) malloc(sizeof(Entry) * k * comm_size);
	}
	split = MPI_Wtime();
	MPI_Gather(entries, k, entrytype, resv_buf, k, entrytype, 0,
			MPI_COMM_WORLD);

	if (rank == 0) {
		HASH_ENTRY_REF_MAP sum;
		fprintf(stderr, "%s %d\n", "Finishing at ", rank);
		for (int i = 0; i < (k * comm_size); i++) {
			if (resv_buf[i].i == -1)
				continue;
			Entry *e;
			if (sum.find(resv_buf[i].hash) == sum.end()) {
				sum.insert(
						make_pair<size_t, Entry*>(resv_buf[i].hash,
								&resv_buf[i]));
			} else {
				Entry *e = sum.find(resv_buf[i].hash)->second;
				e->weight += resv_buf[i].weight;
				e->eps += resv_buf[i].eps;
			}
		}
		HASH_ENTRY_REF_MAP::iterator it;
		ENTRY_VEC entryvec;
		for (it = sum.begin(); it != sum.end(); it++) {
			entryvec.push_back(*it->second);
		}
		sort(entryvec.begin(), entryvec.end(), sort_reverse);
		entryvec.resize(k);
		stringstream ss;
		ss << FILENAME << "_k" << k << ".res";
		FILENAME = ss.str();
		writetofile((char*) FILENAME.c_str(), entryvec);
		free(resv_buf);
	}
	end = MPI_Wtime();
	fprintf(stdout, "%d\t%d\tGAT\t%.8f\n", comm_size, rank, end - split);
	fprintf(stdout, "%d\t%d\tTOT\t%.8f\n", comm_size, rank, end - start);
	free(entries);

	delete crop;
	MPI_Type_free(&entrytype);
	MPI_Finalize();
}

int main(int argc, char *argv[]) {
	CmdParser cp;
	if (!cp.parseargs(argc, argv)) {
		return -1;
	}

	if (cp.a.get < string > (FORMAT) == "dcs") {
		if (cp.a.get < string > (STYPE) == "buf") {
			run<StreamSummaryBuffer,DCS_FORMAT>(argc, argv);
		} else {
			run<StreamSummaryBucket, DCS_FORMAT>(argc, argv);
		}
	} else if (cp.a.get < string > (FORMAT) == "coo"){
		if (cp.a.get < string > (STYPE) == "buf") {
			run<StreamSummaryBuffer,COO_FORMAT>(argc, argv);
		} else {
			run<StreamSummaryBucket,COO_FORMAT>(argc, argv);
		}
	}
}
